package com.autumn.config.services.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.dubbo.config.annotation.Service;
import com.autumn.common.api.application.dto.output.IPageQueryResult;
import com.autumn.config.api.consts.ConfigureConsts.StatusConsts;
import com.autumn.config.api.dto.input.AppPageQueryInput;
import com.autumn.config.api.dto.input.ApplicationProfileInput;
import com.autumn.config.api.dto.input.StatusInput;
import com.autumn.config.api.dto.output.ApplicationProfileOutput;
import com.autumn.config.api.services.IApplicationProfileService;
import com.autumn.config.api.services.ICacheService;
import com.autumn.config.entities.ApplicationInfo;
import com.autumn.config.entities.ApplicationProfile;
import com.autumn.config.entities.ApplicationProfileConnection;
import com.autumn.config.entities.ConnectionConfigure;
import com.autumn.config.repositories.IApplicationInfoRepository;
import com.autumn.config.repositories.IApplicationProfileConnectionRepository;
import com.autumn.config.repositories.IConnectionConfigureRepository;
import com.autumn.mybatis.criterion.LockModeEnum;
import com.autumn.mybatis.criterion.Query;
import com.autumn.util.AutoMapUtils;
import com.autumn.util.AutumnContextUtils;
import com.autumn.util.DataRollbackException;
import com.autumn.util.ExceptionUtils;
import com.autumn.util.data.DbAuditingUtils;
import com.autumn.util.data.DbQueryUtils;
import com.autumn.util.data.PageQueryBuilder;
import com.autumn.util.tuple.TupleTwo;

/**
 * 应用配置服务
 * 
 * @author 老码农
 *         <p>
 *         Description
 *         </p>
 * @date 2018-01-22 00:01:19
 */
@Service(interfaceClass = IApplicationProfileService.class)
@Component
public class ApplicationProfileServiceImpl extends
		AbstractConfigureService<ApplicationProfile, ApplicationProfile, ApplicationProfileInput, ApplicationProfileInput, ApplicationProfileOutput>
		implements IApplicationProfileService {

	@Autowired
	private IApplicationInfoRepository appRepository;

	@Autowired
	private ICacheService cacheService;

	@Autowired
	private IConnectionConfigureRepository connRepository;

	@Autowired
	private IApplicationProfileConnectionRepository refRepository;

	/**
	 * 默认标签
	 */
	public final static String DEFAULT_LABEL = "master";

	/**
	 * 排除的属性集合
	 */
	private final static Map<String, String> EXCLUDE_PROPERTIES = new ConcurrentHashMap<>();

	static {
		String[] names = { "server.port", "spring.application.name", "spring.cloud.config.label",
				"spring.cloud.config.enabled", "spring.cloud.config.profile", "spring.cloud.config.name",
				"spring.cloud.config.discovery.enabled", "spring.cloud.config.discovery.serviceId",
				"eureka.instance.hostname", "eureka.instance.preferIpAddress", "eureka.client.serviceUrl.defaultZone" };
		for (String name : names) {
			EXCLUDE_PROPERTIES.put(name.toLowerCase(), name);
		}
	}

	/**
	 * 是否排除属性
	 * 
	 * @param name
	 *            名称
	 * @return
	 *
	 */
	public static boolean isExcludeProperties(String name) {
		if (name == null) {
			return true;
		}
		return EXCLUDE_PROPERTIES.containsKey(name.toLowerCase());
	}

	/**
	 * 检查配置
	 * 
	 * @param input
	 *            输入
	 * @return
	 */
	private TupleTwo<ApplicationInfo, Set<Long>> checkProfile(ApplicationProfileInput input) {
		ExceptionUtils.checkNotNull(input, "input");
		input.check();
		if (input.getSortId() == null) {
			input.setSortId(1);
		}
		Map<String, Object> proMap;
		if (ApplicationProfile.FORAMT_PROPERTIES.equalsIgnoreCase(input.getProfileFormat())) {
			proMap = AutumnContextUtils.parseProperties(input.getContent(), false);
		} else if (ApplicationProfile.FORAMT_YML.equalsIgnoreCase(input.getProfileFormat())) {
			proMap = AutumnContextUtils.parseYml(input.getContent(), false);
		} else {
			throw ExceptionUtils.throwValidationException("配置格式" + input.getProfileFormat() + " 不支持.");
		}
		for (Map.Entry<String, Object> entry : proMap.entrySet()) {
			String excludeValue = EXCLUDE_PROPERTIES.get(entry.getKey().toLowerCase());
			if (excludeValue != null) {
				throw ExceptionUtils.throwValidationException("配置文件不能包含 " + excludeValue + " 属性，否则导致混乱和不确定性。");
			}
		}
		Map<String, List<String>> refMap = ApplicationProfile.readRefSet(proMap);
		ApplicationInfo appInfo = DbQueryUtils.queryById(appRepository, ApplicationInfo.class, input.getAppId());
		if (appInfo == null) {
			ExceptionUtils.throwValidationException("指定的应用不存在!");
		}
		Set<Long> connectionIdSet = new HashSet<>(refMap.size());
		for (String connectionName : refMap.keySet()) {
			Query query = new Query(ConnectionConfigure.class);
			query.eq("name", connectionName).eq("status", StatusConsts.STATUS_RELEASE).eq("isDelete", false)
					.orderBy("id").lock(LockModeEnum.UPDATE);
			ConnectionConfigure configure = connRepository.selectFirst(query.builderSection());
			if (configure == null) {
				String pName = refMap.get(connectionName).get(0);
				ExceptionUtils
						.throwValidationException("属性 " + pName + " 引用($ref)连接名称 " + connectionName + " 不存在或未发布状状。");
			}
			connectionIdSet.add(configure.getId());
		}
		return new TupleTwo<ApplicationInfo, Set<Long>>(appInfo, connectionIdSet);
	}

	private boolean exist(Long appId, String name, String label) {
		Map<String, Object> propertyMap = new HashMap<>(3);
		propertyMap.put("appId", appId);
		propertyMap.put("name", name);
		propertyMap.put("label", label);
		return this.exist(propertyMap);
	}

	@Transactional(rollbackFor = DataRollbackException.class)
	@Override
	public ApplicationProfileOutput add(ApplicationProfileInput input) {
		TupleTwo<ApplicationInfo, Set<Long>> tuple = checkProfile(input);
		ApplicationInfo app = tuple.getItem1();
		if (this.exist(input.getAppId(), input.getName(), input.getLabel())) {
			ExceptionUtils.throwValidationException(
					String.format("配置名称 %s 标签 %s 在同一应用 %s 已重复。", input.getName(), input.getLabel(), app.getAppName()));
		}
		ApplicationProfile profile = AutoMapUtils.map(input, ApplicationProfile.class);
		if (profile.getSortId() == null) {
			profile.setSortId(1);
		}
		DbAuditingUtils.insertSetProperty(profile);
		profile.setStatus(StatusConsts.STATUS_DRAFT);
		profile.setVersion(1);
		this.updateDefault(profile);
		this.repository.insert(profile);
		this.addRef(tuple.getItem2(), profile, null);
		return AutoMapUtils.map(profile, ApplicationProfileOutput.class);
	}

	@Transactional(rollbackFor = DataRollbackException.class)
	@Override
	public ApplicationProfileOutput modify(ApplicationProfileInput input) {
		if (input == null || input.getId() == null) {
			ExceptionUtils.throwValidationException("Id 不能为空");
		}
		TupleTwo<ApplicationInfo, Set<Long>> tuple = checkProfile(input);
		ApplicationInfo app = tuple.getItem1();
		ApplicationProfile result = this.getById(input.getId(), LockModeEnum.UPDATE);
		if (result == null) {
			ExceptionUtils
					.throwValidationException(String.format("配置名称 %s 标签 %s 不存在。", input.getName(), input.getLabel()));
		}
		if (!input.getName().equalsIgnoreCase(result.getName())
				|| !input.getLabel().equalsIgnoreCase(result.getLabel())) {
			if (this.exist(input.getAppId(), input.getName(), input.getLabel())) {
				ExceptionUtils.throwValidationException(String.format("配置名称 %s 标签 %s 在同一应用 %s 已重复。", input.getName(),
						input.getLabel(), app.getAppName()));
			}
		}
		AutoMapUtils.map(input, result);
		DbAuditingUtils.updateSetProperty(result);
		result.setStatus(StatusConsts.STATUS_DRAFT);
		result.setVersion(result.getVersion() + 1);
		this.updateDefault(result);
		this.repository.update(result);
		this.addRef(tuple.getItem2(), result, result.getId());
		return AutoMapUtils.map(result, ApplicationProfileOutput.class);
	}

	private void addRef(Set<Long> refConnectionSet, ApplicationProfile profile, Long oldprofileId) {
		if (oldprofileId != null) {
			Query query = new Query(ApplicationProfileConnection.class);
			query.eq("profileId", oldprofileId);
			refRepository.deleteByWhere(query.builderSection());
		}
		List<ApplicationProfileConnection> proConnList = new ArrayList<>();
		int i = 1;
		for (Long conectionId : refConnectionSet) {
			ApplicationProfileConnection proConn = new ApplicationProfileConnection();
			proConn.setAppId(profile.getAppId());
			proConn.setConnectionId(conectionId);
			proConn.setProfileId(profile.getId());
			proConn.setSortId(i);
			i++;
			proConnList.add(proConn);
		}
		if (proConnList.size() > 0) {
			refRepository.batchInsert(proConnList);
		}
	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	protected ApplicationProfile deleteById(Long id) {
		ApplicationProfile result = this.getById(id);
		if (result == null) {
			ExceptionUtils.throwValidationException("无法删除不存在的数据。");
		}
		if (result.getStatus().equals(StatusConsts.STATUS_RELEASE)) {
			ExceptionUtils.throwValidationException("不能删除已发布的数据。");
		}
		Query query = new Query(ApplicationProfileConnection.class);
		query.eq("profileId", result.getId());
		refRepository.deleteByWhere(query.builderSection());
		if (this.repository.deleteByPrimaryKey(result.getId()) <= 0) {
			ExceptionUtils.throwValidationException("无法删除不存在的数据。");
		}
		ApplicationInfo appInfo = DbQueryUtils.queryById(appRepository, ApplicationInfo.class, result.getAppId());
		if (appInfo != null) {
			cacheService.deleteServiceProfile(appInfo.getAppId());
		}
		return result;
	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	public ApplicationProfileOutput updateStatus(StatusInput input) {
		ApplicationProfileOutput result = super.updateStatus(input);
		if (result != null) {
			ApplicationInfo appInfo = appRepository.get(result.getAppId());
			if (appInfo != null) {
				result.setServiceId(appInfo.getAppId());
				result.setServiceName(appInfo.getAppName());
				cacheService.deleteServiceProfile(appInfo.getAppId());
			}
		}
		return result;
	}

	@Override
	public IPageQueryResult<ApplicationProfileOutput> queryByPage(AppPageQueryInput input) {
		if (input == null || input.getAppId() == null) {
			ExceptionUtils.throwValidationException("应用Id不能为空");
		}		
		PageQueryBuilder<ApplicationProfile> builder = new PageQueryBuilder<>(ApplicationProfile.class);
		return builder.apply(q -> {
			q.eq("appId", input.getAppId()).orderBy("sortId").orderBy("id");
		}).page(input, this.getPageMaxSize()).toPageResult(this.queryRepository, ApplicationProfileOutput.class);
	}

}
